﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Collections.Concurrent;

namespace Fofia.ObjectPool
{
    public class ObjectPool<T> where T : class, new()
    {
        
        private SemaphoreSlim semaphore;

        /// <summary>
        /// 对象池
        /// </summary>
        private ConcurrentQueue<Item> pool;
        /// <summary>
        /// 处理对象池中过期对象时使用
        /// </summary>
        private ConcurrentQueue<Item> freePool;
        /// <summary>
        /// 借出字典
        /// </summary>
        private ConcurrentDictionary<T, Item> borrowedObjects;
      
        /// <summary>
        /// 定时器，清理过期对象
        /// </summary>
        private Timer timer;

        /// <summary>
        /// 池中最小数量
        /// </summary>
        private int minSize = 1;
        /// <summary>
        /// 对象池大小
        /// </summary>
        private int poolSize = 10;
        /// <summary>
        /// 已借出对象数量
        /// </summary>
        private int borrowedCount = 0;
        /// <summary>
        /// 获取对象超时时长
        /// </summary>
        private TimeSpan borrowTimeout = TimeSpan.FromSeconds(10);
        /// <summary>
        /// 对象未使用过期时间-默认30秒
        /// </summary>
        private int expiration = 30;

        /// <summary>
        /// 对象归还超时时长-默认60秒
        /// </summary>
        private int returnObjectTimeout = 60;
        /// <summary>
        /// 对象初始化器
        /// </summary>
        private Func<T> objectGenerator;

        public ObjectPool()
        {
            PoolOption<T> option = new PoolOption<T>();
            this.minSize = option.MinSize;
            this.poolSize = option.PoolSize;
            this.expiration = option.Expiration;
            this.borrowTimeout= option.BorrowTimeout;
            this.returnObjectTimeout = option.ReturnObjectTimeout;
            this.objectGenerator = option.ObjectGenerator;
           
            Init();
        }

        public ObjectPool(PoolOption<T> option)
        {
            this.minSize = option.MinSize;
            this.poolSize = option.PoolSize;
            this.expiration = option.Expiration;
            this.borrowTimeout = option.BorrowTimeout;
            this.returnObjectTimeout = option.ReturnObjectTimeout;
            this.objectGenerator = option.ObjectGenerator;
            Init();
        }
        /// <summary>
        /// 初始化对象池
        /// </summary>
        /// <param name="poolSize">对象池数量-默认10</param>
        ///<param name="getExpirationSeconds">获取对象过期时间-默认10秒</param>
        /// <param name="expiration">对象过期时间-默认10秒</param>
        /// <param name="objectGenerator">对象初始化操作</param>
        public ObjectPool(int poolSize, int getExpirationSeconds,int expiration, Func<T> objectGenerator)
        {
            if (poolSize > 0)
            {
                this.poolSize = poolSize;
            }
            if (getExpirationSeconds > 0)
            {
                borrowTimeout = TimeSpan.FromSeconds(getExpirationSeconds);
            }
            if (expiration > 0)
            {
                this.expiration = expiration;
            }
            this.objectGenerator = objectGenerator;

            Init();
        }

        /// <summary>
        /// 内部参数初始化
        /// </summary>
        private void Init()
        {
            pool = new ConcurrentQueue<Item>();
            freePool = new ConcurrentQueue<Item>();
            //pool.Enqueue(new T());

            semaphore = new SemaphoreSlim(0, this.poolSize);

            borrowedObjects = new ConcurrentDictionary<T, Item>();
         
            timer = new Timer(state =>
            {
                CleanupExpiredObjects();
            }, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
        }

        /// <summary>
        /// 从对象池中获取对象
        /// </summary>
        /// <returns></returns>
        /// <exception cref="TimeoutException"></exception>
        public T Get()
        {
            Item item;
            if (freePool.TryDequeue(out item)||pool.TryDequeue(out item))
            {

                item.LastTime= DateTime.Now;
                // 从池中获取对象成功
                borrowedObjects.TryAdd(item.Value, item);
                Interlocked.Increment(ref borrowedCount);
                return item.Value;
            }

            if (borrowedCount < poolSize)
            {
                // 未达上限,创建新对象
              
                item = new Item();
                item.LastTime = DateTime.Now;
                item.Value = objectGenerator();
                borrowedObjects.TryAdd(item.Value, item);
                Interlocked.Increment(ref borrowedCount);
                return item.Value;
            }

            // 已达上限,等待获取
            semaphore.Wait(borrowTimeout);
            // 再次尝试从池中获取
            if (freePool.TryDequeue(out item) || pool.TryDequeue(out item))
            {
                item.LastTime = DateTime.Now;
                borrowedObjects.TryAdd(item.Value, item);
               
                Interlocked.Increment(ref borrowedCount);
            }
            else
            {
                throw new TimeoutException("获取对象超时");
            }

            int semaphoreCount = semaphore.Release();
            //Console.WriteLine($"GetObject当前semaphore数量:{semaphoreCount}");
            return item.Value;
        }

        /// <summary>
        /// 归还对象到池
        /// </summary>
        /// <param name="item"></param>
        public void Return(T v)
        {
           
            if (borrowedObjects.TryRemove(v, out Item item))
            {
                Interlocked.Decrement(ref borrowedCount);
                item.LastTime = DateTime.Now;
                pool.Enqueue(item);
               
            }
           
        }

        /// <summary>
        /// 清理归还超时对象
        /// </summary>
        private void CleanupExpiredObjects()
        {
            DateTime now = DateTime.Now;

            //Console.WriteLine($"开始清理过期对象:{DateTime.Now}");
            foreach (var borrowedObject in borrowedObjects)
            {
                DateTime lastTime = borrowedObject.Value.LastTime;
                if ((now - lastTime).TotalSeconds >= returnObjectTimeout)
                {
                    T obj = borrowedObject.Key;
                    if (borrowedObjects.TryRemove(obj, out _))
                    {
                        //pool.Enqueue(obj);
                        //borrowedCount--; //减少borrowedCount
                        Interlocked.Decrement(ref borrowedCount);
                        Console.WriteLine($"清理有借无还对象:{DateTime.Now}，借出去时间：{lastTime}");
                    }

                    if (obj is IDisposable disposableObj)
                    {
                        disposableObj.Dispose();
                    }
                }
            }

            if (pool.Count > this.minSize)
            {
                while (pool.TryDequeue(out Item item))
                {
                    if ((now - item.LastTime).TotalSeconds >= expiration)
                    {
                        if (item.Value is IDisposable disposableObj)
                        {
                            disposableObj.Dispose();
                        }
                        Console.WriteLine($"清理长时间未使用对象:{DateTime.Now},上传归还时间：{item.LastTime}");
                    }
                    else
                    {
                        freePool.Enqueue(item);
                    }
                }

                while (freePool.TryDequeue(out Item item2))
                {
                    //Console.WriteLine($"整理对象池:{DateTime.Now}");
                    pool.Enqueue(item2);
                }
            }

            //Console.WriteLine($"清理过期对象结束:{DateTime.Now}");

            Console.WriteLine($"对象池数据pool:{pool.Count},借出dic数量：{borrowedObjects.Count}");
        }

        /// <summary>
        /// 清空对象池
        /// </summary>
        public void Clear()
        {
            foreach (var borrowedObject in borrowedObjects)
            {
                T obj = borrowedObject.Key;
                if (borrowedObjects.TryRemove(obj, out _))
                {
                    //pool.Enqueue(obj);
                    // borrowedCount--; //减少borrowedCount
                    Interlocked.Decrement(ref borrowedCount);
                }

                if (obj is IDisposable disposableObj)
                {
                    disposableObj.Dispose();
                }
            }

          
            pool.Clear();

        }


        public class Item {
            public T Value { get; set; }

            public DateTime LastTime { get; set; }
        }
        
    }



}